package com.gmrz.uaf.protocol.v1.json;

import com.google.gson.InstanceCreator;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.inject.Inject;
import com.gmrz.uaf.common.TLVTag;
import com.gmrz.uaf.common.UAFRuntimeException;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.ServerData;
import com.gmrz.util.Convert;
import com.gmrz.util.Strings;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ServerDataAdapter implements InstanceCreator<ServerData>,
		JsonSerializer<ServerData>, JsonDeserializer<ServerData> {
	private static final Logger LOG = LogManager
			.getLogger(ServerDataAdapter.class);

	@Inject
	private static CryptoEngine cryptoEngine = null;

	public ServerData createInstance(Type type) {
		return new ServerData();
	}

	public JsonElement serialize(ServerData src, Type typeofSrc,
			JsonSerializationContext context) throws JsonIOException {
		try {
			addTLVSegmentsTo(src);
			byte[] data = src.getTLVBytes();

			data = cryptoEngine.getServerDataCipherHandler().encrypt(data);
			String encoded = Convert.toBase64(data);
			return context.serialize(encoded);
		} catch (Exception ex) {
			LOG.error("Error creating the ServerData TLV data, ", ex);
			throw new JsonParseException(
					UAFErrorCode.PROTOCOL_TLV_PARSING_FAILED.toString(), ex);
		}
	}

	private void addTLVSegmentsTo(ServerData src)
			throws UnsupportedEncodingException {
		if ((src.challenge == null) || (src.challenge.length == 0)) {
			throw new UAFRuntimeException(
					"Challenge can not be null or empty for server data.");
		}

		src.addTLVSegment(TAG.CHALLENGE.getTag().getValue(), TAG.CHALLENGE
				.getTag().forByteArray(src.challenge));

		src.addTLVSegment(TAG.TIMESTAMP.getTag().getValue(), TAG.TIMESTAMP
				.getTag().forLong(src.timestamp));

		if (!Strings.isNullOrEmpty(src.bindData)) {
			src.addTLVSegment(TAG.BIND_DATA.getTag().getValue(), TAG.BIND_DATA
					.getTag().forString(src.bindData));
		}

		if (!Strings.isNullOrEmpty(src.userName)) {
			src.addTLVSegment(TAG.USERID.getTag().getValue(), TAG.USERID
					.getTag().forString(src.userName));
		}

		if (Strings.isNullOrEmpty(src.policyName)) {
			throw new UAFRuntimeException(
					"Policyname can not be null or empty for server data.");
		}
		src.addTLVSegment(TAG.POLICYNAME.getTag().getValue(), TAG.POLICYNAME
				.getTag().forString(src.policyName));

		if ((src.getTransactionHashSet() != null)
				&& (!src.getTransactionHashSet().isEmpty())) {
			for (String str : src.getTransactionHashSet()) {
				src.addTLVSegment(TAG.TXHASH.getTag().getValue(), TAG.TXHASH
						.getTag().forString(str));
			}
		}
		if(!Strings.isNullOrEmpty(src.getValidateDataHashToString())){
			src.addTLVSegment(TAG.VALIDATEHASH.getTag().getValue(), TAG.VALIDATEHASH
					.getTag().forString(src.getValidateDataHashToString()));
		}

		src.addTLVSegment(TAG.OPERATIONID.getTag().getValue(), TAG.OPERATIONID
				.getTag().forByte(src.operationId));
	}

	public ServerData deserialize(JsonElement json, Type typeofT,
			JsonDeserializationContext context) throws JsonParseException {
		ServerData retval = new ServerData();
		String strData = json.getAsString();
		try {
			byte[] data = Convert.fromBase64(strData);

			data = cryptoEngine.getServerDataCipherHandler().decrypt(data);
			ByteBuffer buffer = ByteBuffer.wrap(data).order(
					ByteOrder.LITTLE_ENDIAN);
			while (buffer.hasRemaining()) {
				TAG t = TAG.forValue(TLVTag.readTag(buffer));
				switch (t.getTag().getValue()) {
				case 0:
					retval.setChallenge(t.tag.parseByteArray(buffer));
					break;
				case 1:
					retval.setTimestamp(t.tag.parseLong(buffer));
					break;
				case 2:
					retval.setUserName(t.tag.parseString(buffer));
					break;
				case 3:
					retval.setOperationId(t.tag.parseByte(buffer));
					break;
				case 4:
					retval.setPolicyName(t.tag.parseString(buffer));
					break;
				case 5:
					retval.setBindData(t.tag.parseString(buffer));
					break;
				case 6:
					retval.addTransactionHash(t.tag.parseString(buffer));
					break;
				case 7:
					retval.setStringToValidateDataHash(t.tag.parseString(buffer));
					break;
				default:
					throw new RuntimeException(
							"ServerData contains some unrecognized data, rejecting the response");
				}
			}

		} catch (Exception ex) {
			LOG.error("Error parsing the ServerData TLV data, ", ex);
			throw new JsonParseException(
					UAFErrorCode.PROTOCOL_TLV_PARSING_FAILED.toString(), ex);
		}
		return retval;
	}

	enum TAG {
		CHALLENGE(0), TIMESTAMP(1), USERID(2), OPERATIONID(3), POLICYNAME(4), BIND_DATA(
				5), TXHASH(6),VALIDATEHASH(7);

		TLVTag tag = null;

		TAG(int id) {
			this.tag = new TLVTag((short) id);
		}

		public TLVTag getTag() {
			return this.tag;
		}

		public static TAG forValue(short b) {
			for (TAG t : values()) {
				if (b == t.tag.getValue())
					return t;
			}
			throw new IllegalArgumentException(
					"Invalid TLV tag value in the server data: " + b);
		}
	}
}
